
import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:klondike/klondike/klondike_game.dart';
import 'package:klondike/klondike/klondike_world.dart';

import '../../components/card.dart';
import '../../components/operation.dart';
import '../../components/pile.dart';
import '../../util/effect_utils.dart';

class TableauPile extends PositionComponent
  with TapCallbacks, HasWorldReference<KlondikeWorld>
  implements Pile{
  
  TableauPile({super.position}) : super(size: KlondikeGame.cardSize);

  final _borderPaint = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 10
    ..color = const Color(0x50ffffff);

  @override
  void render(Canvas canvas) {
    canvas.drawRRect(KlondikeGame.cardRRect, _borderPaint);
  }

  final List<Card> _cards = [];

  final Vector2 _fanOffset = Vector2(0, KlondikeGame.cardHeight * 0.16);

  bool flipTopCard() {
    if(_cards.isNotEmpty && _cards.last.isFaceDown) {
      _cards.last.flip();
      layoutCards();
      return true;
    } else {
      return false;
    }
  }

  @override
  bool canMoveCard(List<Card> cards) {
    int rankVal = -1;
    for(Card card in cards) {
      if(card.isFaceDown) {
        return false;
      }

      if(rankVal == -1) {
        rankVal = card.rank.value;
      } else if(rankVal == card.rank.value + 1) {
        rankVal = card.rank.value;
      } else {
        return false;
      }
    }
    return true;
  }

  @override
  bool canAcceptCard(List<Card> cards) {
    Card card = cards.first;

    if(_cards.isEmpty) {
      return card.rank.value == 13;
    } else {
      final topCard = _cards.last;
      // to
      return topCard.suit.isRed != card.suit.isRed && 
        topCard.rank.value == card.rank.value + 1;
    }
  }

  @override
  void removeCard(List<Card> cards) {
    for(Card card in cards) {
      assert(_cards.contains(card) && card.isFaceUp);
      _cards.remove(card);
    }

    layoutCards();
  }

    @override
  void acquireCard(List<Card> cards) {

    for(Card card in cards) {
      if (_cards.isEmpty) {
        card.position = position;
      } else {
        card.position = _cards.last.position + _fanOffset;
      }
      card.priority = _cards.length;
      _cards.add(card);
      card.pile = this;
    }
    layoutCards();
  }

  @override
  void returnCard(List<Card> cards) {

    Card firstCard = cards.first;

    final index = _cards.indexOf(firstCard);
    var curPosition =
        index == 0 ? position : _cards[index - 1].position + _fanOffset;
    var curPriority = index;

    for(Card card in cards) {
      card.position = curPosition.clone();
      card.priority = curPriority;
      
      curPosition = curPosition + _fanOffset;
      curPriority = curPriority + 1;
    }

    layoutCards();
  }

  @override
  PositionComponent top() => _cards.isNotEmpty ? _cards.last : this;

  final Vector2 _fanOffset1 = Vector2(0, KlondikeGame.cardHeight * 0.05);
  final Vector2 _fanOffset2 = Vector2(0, KlondikeGame.cardHeight * 0.16);

  void layoutCards() {
    if(_cards.isEmpty) {
      return;
    }

    _cards[0].position.setFrom(position);
    for(var i=1; i<_cards.length; i++) {
      _cards[i].position
        ..setFrom(_cards[i-1].position)
        ..add(_cards[i-1].isFaceDown ? _fanOffset1 : _fanOffset2);
    }

    height = KlondikeGame.cardHeight * 1.5 + _cards.last.y - _cards.first.y;
  }

  List<Card> cardsOnTop(Card card) {
    assert(card.isFaceUp && _cards.contains(card));
    final index = _cards.indexOf(card);
    return _cards.getRange(index+1, _cards.length).toList();
  }

  @override
  Future<void> handleTapUp(Card card) async {

    var cards = [card];
    cards.addAll(cardsOnTop(card));
    
    if(canMoveCard(cards)) {
      if(! await tryToIntegrate(cards)) {
        bool canMoveToStack = false;
        for(TableauPile tableauPile in world.tableauPiles) {
          if(tableauPile == this) {
            continue;
          }
          if(tableauPile.canAcceptCard(cards)) {
            canMoveToStack = true;

            Operation operation = Operation.init(
              cards,
              this,
              tableauPile,
              cards.map((card) => card.position.clone()).toList(),
              tableauPile.fetchPositions(cards),
              20.0);
            
            await operation.proceed();
            fillFlip(operation);

            world.histories.add(operation); // record

            break;
          }
        }

        if(!canMoveToStack) {
          EffectUtils.shake(cards);
        }
      }
    }
  }

  Future<bool> tryToIntegrate(List<Card> cards) async {
    final suitIndex = cards.first.suit.value;
    if (world.foundations[suitIndex].canAcceptCard(cards)) {
      Operation operation = Operation.init(
        cards,
        this,
        world.foundations[suitIndex],
        cards.map((card) => card.position.clone()).toList(),
        [world.foundations[suitIndex].position],
        40.0);
      
      await operation.proceed();
      fillFlip(operation);
      world.histories.add(operation); // record
      return true;
    } else {
      return false;
    }
  }

  List<Vector2> fetchPositions(List<Card> cards) {
      List<Vector2> positions = [];
      double width;
      double height;
      if(_cards.isEmpty) {
        width = position.x;
        height = position.y;
      } else {
        width = _cards.last.position.x;
        height = _cards.last.position.y + _fanOffset2.y;
      }
        
      for(int i=0; i<cards.length; i++) {
        positions.add(Vector2(width, height));
        height += _fanOffset2.y;
      }

      return positions;
  }

  void fillFlip(Operation operation) {
    if(flipTopCard()) {
        operation.preRevoke = () {
          if(_cards.isNotEmpty && _cards.last.isFaceUp) {
            _cards.last.flip();
            layoutCards();
          }
        };
      }
  }

  @override
  Future<bool> tryToMove() async {

    if(_cards.isEmpty) {
      return false;
    }

    Card pCard = _cards.last;
    List<Card> cards = [pCard];
    for(int i=_cards.length-2; i>=0; i--) {
      Card qCard = _cards[i];
      if(qCard.isFaceUp 
        && qCard.suit.isRed != pCard.suit.isRed 
        && qCard.rank.value == pCard.rank.value+1) {
          cards.add(qCard);
          pCard = qCard;
      } else {
        break;
      }
    }

    if(cards.length > 1) {
      cards = cards.reversed.toList();
    }

    
    if(canMoveCard(cards)) {
      final suitIndex = cards.first.suit.value;
      if (cards.length == 1 && world.foundations[suitIndex].canAcceptCard(cards)) {
        await world.highlight(cards, [world.foundations[suitIndex].top()]);
        return true;
      } else {
        for(TableauPile tableauPile in world.tableauPiles) {
          if(tableauPile == this) {
            continue;
          }

          if(tableauPile.canAcceptCard(cards)) {
            await world.highlight(cards, [tableauPile.top()]);
            return true;
          }
        }
      }
    }

    return false;
  }
}